ci(audit): restore strict Bun advisory policy - #9773
Conversation
Yarn 1.x is unmaintained. The root install in CI already runs bun (`.github/actions/install/action.yml`); audit, size-report, flakiness, release, and a few helper actions still shelled out to yarn. Make the contract explicit so contributor installs match what CI runs: a root `bunfig.toml` that forces the text lockfile and hoisted linker, the lockfile committed (removed from `.gitignore`), and the install action's cache step renamed (`yarn-cache` -> `install-cache`) and keyed on `bun.lock`. `audit.yml` swaps both `yarn audit` calls (root and vendor/) to `bun audit`. The advisory set may differ; review and pin a baseline if anything trips.
The per-plugin sandbox install relied on `workspaces.nohoist: ['**/**']` (yarn 1) plus `installConfig.hoistingLimits = 'workspaces'` (yarn 2/3 for aerospike) to keep each `versions/<plugin>@<ver>/` sandbox isolated from the others. Bun's `--linker=isolated` is the structural equivalent and is now pinned in `versions/bunfig.toml` instead of re-declared in every synthesized `package.json`. Pre-resolving each declared range to its highest published version before writing the synthesized `package.json` keeps matrix coverage equivalent to yarn 1's prior behaviour. Yarn 1 picked the highest matching version on each install; bun picks the lowest. Without pre-resolution the raw-suffix sandboxes (`pino@4`, `mongodb@5`, `pino@>=6.8.0`, ...) collapse onto the same versions as the coerced ones (`pino@4.0.0`, `mongodb@5.0.0`, ...). The helper calls `bun pm view <pkg>@<range> version --json`, caches per process, and falls back to install-time resolution with a warning on registry failure. `scripts/test/install-plugin-modules.spec.js` spawns the install script with yarn removed from `$PATH` and deep-equals each pino sandbox's resolved version against its declared range. `.github/workflows/install-smoke.yml` runs that test plus a cheap pino plugin spec on every PR.
Three coordinated pieces: 1. 17 workflow/action files swap every `yarn run <script>` for `npm run <script>`, and `integration-tests/esbuild/package.json`'s `link` script swaps `yarn link` for `npm link`. The `yarn config set ignore-engines true` steps and per-line `--ignore-engines` flags go with them — yarn 1 enforced `engines.node` strictly, bun does not. 2. `scripts/check_licenses.js` walks `bun.lock` and `vendor/package-lock.json` directly instead of shelling out to `yarn list` + `npm list`. The lockfiles record the union of installed packages across platforms; `npm ls --omit=dev` only lists the optionals installed on the current host, so on Darwin/arm64 the cross-platform `@oxc-parser/binding-*` and its native helper transitives would report extraneous against the CSV. 3. `scripts/verify-exercised-tests.js` drops its yarn-detection paths now that workflows reference scripts only via `npm run`. `platform.yml`'s `yarn` and `yarn-berry` matrix entries stay untouched — they cover the user-PM contract, not the dev tooling.
Yarn 1.x is unmaintained. The docs build was the last install path still on yarn. `docs/yarn.lock` is replaced with `docs/bun.lock` (text format); TypeDoc and tsc still drive the build, so generated docs are unchanged.
Yarn 1.x is unmaintained. The lockfile, the dedupe-bot jobs, the husky hook, and the `yarn-deduplicate` devDep all exist only to keep yarn 1 healthy; with installs, run-scripts, and docs already on bun and npm, they are accommodations to nothing. A cleanup without a regression guard rots — yarn slips back in the next time a workflow or contributor PR is reviewed without the migration in mind. Three coordinated pieces: 1. Delete the dead yarn machinery: `yarn.lock`, `.yarnrc`, the `yarn-dedupe`/`yarn-dedupe-push` jobs in `project.yml` and their STS policy file under `.github/chainguard/`, the husky pre-commit hook, the `dependencies:dedupe` script and `yarn-deduplicate` devDep, the matching entry in the sirun startup require-preload list, and the `yarn-cache-` prefix on the install-action cache key (already keyed off `bun.lock`). 2. Update `.gitignore` (drop `yarn-debug.log*`, `yarn-error.log*`, `.yarn-integrity`; keep the `**/yarn.lock` entries the `next` plugin and appsec next fixtures still emit), the contributor docs (`CONTRIBUTING.md`, `AGENTS.md`), and the apm-integrations agent skill files. 3. Add `scripts/test/no-yarn-dev-references.spec.js`. Walks `git ls-files`, allowlists the four intentional yarn-reference categories (product-code user-PM detection, user-PM test fixtures, user-facing docs, and the regression-test specs that name yarn literally), and `assert.deepStrictEqual`s the offending list against `[]`. The `test:scripts` glob in `package.json` already covers it. Drive-by fix: * Drop the `bluebird@3.7.2` transitive entry from `packages/dd-trace/test/plugins/externals.js` under `moleculer`. Moleculer 0.14 (the fixture's pinned line) dropped its bluebird dep, so the entry installs an unused package. * Tighten `release.yml`'s two docs-job `bun install` calls to `--frozen-lockfile`; both lockfiles are committed.
The packed `dd-trace.tgz` does not include `vendor/package-lock.json` (the `files` field ships pre-built `vendor/dist/**` artifacts only), so the `prepare` script (`cd vendor && npm ci --include=dev`) cannot re-bootstrap vendor in a consumer install context. Yarn 1's `--prod` mode skipped lifecycle scripts altogether; bun runs them by default. `--ignore-scripts` matches the prior behaviour, and the script is not needed for measuring the published package's install size.
Three coordinated fixes for the per-plugin sandbox install:
1. `--linker=isolated` placed every dependency under
`versions/node_modules/.bun/<pkg>@<ver>/` and only symlinked the
declared dep into each sandbox. Cross-workspace `require()` walks
from inside one sandbox could not reach a sibling's package, so
moleculer's runtime `require('bluebird')` fallback returned
`Cannot find module`. Switching to `linker = "hoisted"` lifts
shared deps to `versions/node_modules/<pkg>` while still nesting
different versions under their owning sandbox; matrix isolation
holds and the cross-workspace lookups resolve.
2. Bun runs `install` / `postinstall` only for packages listed in
the workspace root's `trustedDependencies`. Native plugins
(`aerospike`, `@confluentinc/kafka-javascript`, `pg-native`, ...)
installed without their `.node` files compiled, so `bindings`
threw `Could not locate the bindings file` at test time. The
script now collects every package name it installs (workspaces
plus peer-dep injections) and writes them to
`versions/package.json:trustedDependencies`.
3. `assertPeerDependencies` was writing the raw range from the
parent's `peerDependencies`/`devDependencies` straight into the
sandbox `package.json`. Bun resolves ranges to the lowest
matching version where yarn 1 picked the highest, so peer deps
like `@smithy/node-http-handler` (only present in newer
`@aws-sdk` releases) landed on an older sibling that did not
ship them. Pre-resolving each range via `bun pm view` matches
the prior coverage.
`versions/bunfig.toml` moves from a committed file (with a
`.gitignore` exception) to a script-generated one — single source of
truth alongside the workspace `package.json`. The regression test
walks Node's resolution from inside each sandbox via `createRequire`
so it passes against both hoisted and isolated layouts.
Moleculer's transit/util layer falls back to `require('bluebird')`
at runtime even in 0.14.x. Dropping the externals fixture entry on
the strength of moleculer's manifest alone (which does not list
bluebird) broke the plugin's tests with `Cannot find module
'bluebird'` across seven test cases.
Restored as a sibling sandbox; bun's hoisted linker lifts it into
`versions/node_modules/bluebird` where moleculer's `require()` walk
finds it.
`--linker=hoisted` lifts a single copy of each package to
`versions/node_modules/<pkg>`, which broke five plugin spec files
(`next`, `kafkajs`, `rhea`, `memcached`, `apollo`) that hard-code
paths like `versions/<plugin>@<ver>/node_modules/<plugin>/<internal>`:
the highest-version sandbox loses its nested copy under hoisted, so
the require fails with `Cannot find module`. Bun's `--linker=isolated`
keeps a per-sandbox symlink at exactly that path and resolves it
through the central `.bun/` store; the hard paths work again.
Cross-workspace `require()` lookups (moleculer's runtime
`require('bluebird')` fallback was the canary) now go through the
externals fixture's `dep: true, forced: true` shape: bluebird is
injected as a direct dep of each moleculer sandbox, so bun's
isolated layout symlinks it into
`versions/moleculer@<ver>/node_modules/bluebird` where moleculer's
require walk from the central store finds it.
This fixes the `Channel credentials must be a ChannelCredentials object` failure across all 22 `@google-cloud/pubsub@1.2.0` plugin specs under bun's isolated linker. `pubsub@1.2.0`'s `pubsub.js` source-requires `@grpc/grpc-js` when `PUBSUB_EMULATOR_HOST` (or `apiEndpoint`) is set, but the manifest does not declare it. The parent walk lands on whatever bun hoists at the workspace root — currently the 1.8+ tree shared with `google-gax@3.5.7` — and the credentials produced there fail the `instanceof ChannelCredentials` check inside 1.3.x's Channel constructor, the version pubsub@1.2.0's nested `google-gax@1.15.4` uses (`@grpc/grpc-js: ~1.3.6`). yarn 1 hoisted 1.3.x at the right place; bun does not. `assertPeerDependencies` already injects forced direct deps into every sandbox of a named package — moleculer's bluebird is the precedent. The existing `latests[name]` source ships `@grpc/grpc-js@1.14.3`, too new for `google-gax@1.15.4`; add an entry-level `version` override and inject `@grpc/grpc-js: ~1.3.6` for every `@google-cloud/pubsub` sandbox so bun hoists 1.3.x at the workspace root.
This fixes the langchain plugin tests under bun's isolated linker. The `langchain@>=0.1` sandbox cannot resolve `@langchain/openai` at all - bun isolated does not hoist transitive deps to the workspace root - and the only other source for the require, the `latests` pin in `packages/dd-trace/test/plugins/versions/package.json` (`1.2.2`), throws `Cannot use 'in' operator to search for 'output_version' in undefined` in the chat-model converter when paired with a `@langchain/core@0.1.0` BaseMessage, and otherwise sends a 1.x request shape that misses every recorded cassette and 401s on the proxy fallback. Yarn classic happened to hoist `@langchain/openai@0.0.34` (the transitive dep of `langchain@0.1.0`) to the workspace root, so every `langchain@<version>` sandbox resolved to that version via NODE_PATH walking and the recorded cassettes match its `OpenAI/JS 4.x` request shape. Pinning the same version explicitly in the generated `versions/package.json` puts bun's isolated layout on that version too. Recording cassettes against the newer `@langchain/openai` is a follow-up that earns its own change.
Both fixup commits added inline comments explaining the bun-vs-yarn hoisting difference; the no-yarn-dev-references guardrail flagged them because cross-PM contrast prose belongs in the commit body, not in code (per the comment-skip rule for "accuracy depends on code outside this file"). The structural why — bun's isolated linker does not hoist transitives, so we pin the missing transitive directly — is what stays in the comments; the historical contrast is gone.
Three holes in the yarn → bun migration of `versions/` surfaced in CI:
1. `pg-native` is in `trustedDependencies` so its install script
runs, but the actual `node-gyp` build sits in `libpq` (a
transitive). Bun's `trustedDependencies` does not cascade, so
`libpq`'s `addon.node` never builds and the `bindings` package
rejects the load at test time. Add `libpq` directly to the
trusted set.
2. `pkgJsonPath()` in the generated sandbox `index.js` resolved
`<name>/package.json`, which throws `ERR_PACKAGE_PATH_NOT_EXPORTED`
for packages that ship an `exports` map without a `./package.json`
entry (moleculer, react, ...). Walk `module.paths` directly so
the lookup stays exports-blind, mirroring `requirePackageJson`.
3. The bedrock-runtime LLMObs spec reaches into the workspace's own
`@smithy/node-http-handler` via `.get('@smithy/node-http-handler')`.
Under the isolated linker that transitive sits only inside
aws-sdk's private store and is not reachable from the workspace
root, so inject it as a direct dep of every
`@aws-sdk/client-bedrock-runtime` sandbox via the existing
`dep: true, forced: true` channel.
`dd-license-attribution` only enumerates root deps from `yarn.lock` or `package-lock.json`; the migration ships only `bun.lock`, so the upstream tool drops every root runtime/optional row on each run and the auto-update workflow fails on every PR. Until bun.lock support lands upstream (https://github.com/watson/dd-license-attribution), `scripts/check_licenses.js` (run via `npm run lint`) is the source of truth for CSV completeness against `bun.lock`, `vendor/package-lock.json`, and `vendored-dependencies.csv`. The workflow now does the bare minimum needed to keep the required check name green — assert that `LICENSE-3rdparty.csv` is present and non-empty on the PR head. The trigger no longer fires on `bun.lock` changes since we have no way to act on them; it stays on the vendor and vendored-deps paths so future tooling work has somewhere to plug back in.
Bun's isolated linker keeps a single shared copy of every package under `versions/node_modules/.bun/<name>@<ver>/`, so a native binding compiled by `pg-native` → `libpq`'s install hook under one Node major is reused verbatim on the second `npm run services` invocation under a different Node major. The Plugin and Instrumentation actions install once with the oldest LTS, then switch to the latest Node and run the same sandbox a second time — the cached `addon.node` then crashes on load with `undefined symbol: _ZN2v86Object16SetInternalFieldEiNS_5LocalINS_5ValueEEE`. Yarn 1's per-workspace layout rebuilt every binding on every install pass and never hit this. Wipe the central store (and the matching `bun.lock`) when the recorded Node ABI does not match the current process so the next `bun install --trust` reruns lifecycle scripts and rebuilds against the live runtime.
The workflow header ran afoul of the new `no-yarn-dev-references` spec that scans tracked files for `\byarn\b`. Reword the comment to explain the same constraint without using the word.
Two unrelated yarn → bun differences hide the same way: the test
process deadlocks for hours in `should not alter the default behavior
with pretty print` for pino@5/6/7, and the langchain `JSON message
input` regression rejects every plain `{role, content}` payload with
`messageLike is not iterable`. Both come down to bun installing a
different transitive than yarn classic did.
1. `bun pm view <pkg>@<range> version` returns the lowest matching
version, not the highest. `resolveLatestSatisfying` now pulls the
full version list and runs `semver.maxSatisfying` to mirror yarn
classic's choice. Same effect across the matrix: every cap that
yarn pinned to its highest under-cap version pins to that version
again under bun. Concretely, `@langchain/core@>0.1.56 <0.3.0`
resolves to `0.2.36` instead of `0.1.63`, where
`coerceMessageLikeToMessage` accepts JSON messages.
2. Bun's isolated linker keeps a central
`versions/node_modules/.bun/node_modules/` directory of one
symlink per package, picking the highest installed version
across every workspace. From inside `.bun/pino@5.0.0/.../pino/lib/tools.js`,
Node's resolution walks up into that directory and finds the
central `pino-pretty@13.1.3` symlink before falling through to
`NODE_PATH`'s per-workspace `pino-pretty@1.0.1`. Pino@5's pretty
integration then crashes with `pretty is not a function`, the
throw happens inside an internal pino write loop and never
surfaces a mocha failure — the runner just sits forever. Wipe
the central directory after every `bun install --trust` so
per-workspace resolution wins.
The benchmarking-platform image is rebuilt from `master` and does not yet ship bun, so every dev-tooling install path the benchmark exercises (`bun install` at the repo root, `npm run services` ↦ `scripts/install_plugin_modules.js` ↦ `bun install --trust`, `benchmark/sirun/runall.sh`'s own bootstrap) fails the moment bun is invoked. Add a `before_script` to `.benchmarks` that installs bun once per job and exports `$HOME/.bun/bin` on `PATH`, and move the runall.sh bootstrap out of its subshell so `PATH` stays set for the sirun loop instead of leaking with the subshell exit. Drop the benchmarks.yml `before_script` once the image bakes bun in.
`devflow/mergegate` rejects PRs that introduce a tracked file with no `CODEOWNERS` entry. `.github/workflows/install-smoke.yml` was added in this PR and exercises the lang-platform-js sandbox-install path, so route it to the same team that owns `/scripts/**`.
…to-sts `dd-license-attribution` does not yet read `bun.lock`, so the auto-update workflow had been disabled while the migration landed. Replace the upstream tool with `scripts/generate-3rdparty-licenses.js`, which walks the same locks and CSV the lint check already trusts (`bun.lock`, `vendor/package-lock.json`, `.github/vendored-dependencies.csv`) and fetches license + origin metadata from the npm registry. The script preserves rows that already exist in `LICENSE-3rdparty.csv` to keep the diff tight on re-runs and records the project itself from `package.json` so the self-row stays stable across registry hiccups. The workflow restores the original two-stage shape: `check-licenses` regenerates the CSV and uploads it as an artifact when the bot owns the PR; `auto-commit-licenses` mints a token via `DataDog/dd-octo-sts-action` (governed by the existing `update-3rdparty-licenses.sts.yaml` chainguard policy) and pushes the file via the GitHub API. Human PRs that drift the CSV continue to fail with the regen instructions, same shape as before. The matching CSV update sorts the two vendored entries (`aws-lambda-nodejs-runtime-interface-client`, `is-git-url`) into their alphabetical position so the regen and the committed file match byte-for-byte going forward.
The plugin smoke test is a real test surface; it should show up in Datadog Test Optimization the same way the full plugin matrix does. Mint a Datadog API key via `dd-sts-api-key` and call the shared `push_to_test_optimization` action under `if: !cancelled()`, so a failed install or smoke run still uploads its junit artifact for triage.
The previous fix wiped `versions/node_modules/.bun/node_modules/` wholesale, which silenced the `pino-pretty` shadow but also removed the central symlinks every other sandbox relied on for transitive resolution. `q@2`'s `collections`, `@grpc/grpc-js`'s `@grpc/proto-loader`, and `knex@0.8`'s `sqlite3` lookups all walk through that central store, and the sandbox install jobs for them broke as a side effect. Tighten the pruner to remove central symlinks only for packages that have more than one major installed in the `.bun/` store. That keeps hoisted resolution intact for single-version transitives (`@grpc/proto-loader@0.x`, `sqlite3@5.x`, `libpq@1.x`, ...) while still forcing per-workspace resolution for packages whose central pin shadows an incompatible major that a specific sandbox needs (`pino-pretty@13.x` shadowing `pino@5`'s `pino-pretty@1.0.1`). Drop the leftover yarn references from the regen script's JSDoc so the `no-yarn-dev-references` regression test stays green.
`q@2.0.0`'s manifest declares `collections: ^2.0.0`, but `q.js` does
`require('collections/shim')` and `shim.js` only exists in
`collections@>=5`. The previous package manager's flat hoist always
served `collections@5` from the workspace root, so q's resolution
walked up to it. Bun's isolated linker honours the `^2.0.0` range
and lands `collections@2.0.3` in q's per-package store, where the
require throws `Cannot find module 'collections/shim'`.
Pin `collections` to `^5.0.0` via the workspace `overrides` field so
every q sandbox lands on the same version the previous tooling
served. Other consumers in the workspace already declare or transit
via 5.x, so the override does not regress anything else.
CodeQL flagged the `author.replace(/\s*<[^>]+>\s*/, '')` regex as a potential HTML-injection sink because the npm-registry-supplied `author` / `contributors` strings are technically untrusted input. Split on the first `<` or `(` instead — npm's `Name <email> (url)` shape is well-defined enough that a literal slice gives the same result without the regex pattern that CodeQL warns about. Same output for every package in the current CSV, so the regen produces byte-identical rows.
The vertex-ai plugin spec stubs `GoogleAuth.getAccessToken` via
`require('versions/@google-cloud/vertexai@<ver>').get('google-auth-library/...')`.
`google-auth-library` is a regular transitive of `@google-cloud/vertexai`,
so under bun's isolated linker it lives only inside vertexai's private
store and isn't reachable from the workspace root. The previous package
manager's flat hoist masked this; bun didn't, and the spec hung at the
first `before` hook because `require.resolve` walks until it gives up.
Inject it as a direct dep of every `@google-cloud/vertexai` sandbox via
the same `dep: true, forced: true` channel that fixes the bedrock spec
right above it.
The `bun.sh/install` script unzips the release archive, so the install fails with `error: unzip is required to install bun` on the benchmarking-platform image because `ubuntu:22.04` does not ship `unzip`. Install it via `apt-get` only when missing — keeps the fast path on hosts that already have it (sirun's `runall.sh` bootstrap, eventually the image once it bakes bun in).
`docs/bun.lock` was added in this PR (replacing `docs/yarn.lock`) and devflow/mergegate flags any tracked file with no owner. The `type:doc:build` / `type:doc:test` scripts use the same toolchain as the rest of `/scripts/**`, so route the directory to the same team.
`@langchain/openai@0.0.34`'s manifest declares
`@langchain/core: >0.1.56 <0.3.0`. The langchain regression spec
(`instruments a langchain openai chat model call for a JSON message
input`) only works against a `0.2.x` core — the older
`coerceMessageLikeToMessage` in `0.1.x` only knows the
`[role, content]` tuple shape and crashes on the spec's
`{role, content}` object input.
Bun's isolated linker resolves that range deterministically per
host, but the chosen version varies: it lands `0.2.36` on the local
macOS dev machine and `0.1.63` on the github-hosted runner image.
The previous package manager always served the highest workspace
copy via flat hoist, so this never surfaced. Pin the floor for the
`@langchain/openai@0.0.34/@langchain/core` pair to `^0.2.0` so both
hosts agree, without affecting the `@langchain/openai@1.x.x` peer
constraint resolved elsewhere in the workspace.
`ai@4.0.2` declares `zod` as an optional peer (`^3.0.0`); `@ai-sdk/openai` @1.3.23+ as a required peer. Yarn 1's flat hoist served the standalone `zod` workspace's copy from the workspace root to both sandboxes; bun's isolated linker honours each package's own manifest and skips optional peers, so `versions/ai@4.0.2` lands without `zod` and the first `ai.generateText` invocation throws `Cannot find module 'zod'` from inside the SDK's response-parsing path. Inject `zod` as a direct dep of every `ai` sandbox via the existing `dep: true` channel so bun materialises it alongside `ai` in the isolated store.
Bun's isolated linker materializes only a package's declared dependencies, so two specs that reach past the package their sandbox installs broke: 1. Every `@openai/agents*` package imports its `zod` peer at load time, and an unmet peer is absent from the store entry the sandbox resolves to. 2. The lambda spec reads `@aws-sdk/core`'s version to decide whether the clock-skew guard still exists, but that transitive lives in the client's own store entry rather than the sandbox. The openai-agents setup also anchored `openai` at a hoisted path the isolated layout never creates. Resolving from the loaded `@openai/agents-openai` build pins the copy that build itself uses.
The LLMObs spec anchored `openai` at `versions/node_modules/@openai/agents-openai`, a hoisted path bun's isolated linker never creates, so the suite failed at its `before` hook with `Cannot find module 'openai'`. Resolving from the loaded `@openai/agents-openai` build pins the copy that build itself uses, the same way the APM plugin setup now does.
Five `>> $GITHUB_OUTPUT` redirects in this workflow were left unquoted, which shellcheck flags as SC2086 and which master already cleared everywhere else ahead of enabling the rule.
1. Keep the version bump at HEAD on release proposal branches by preventing license auto-commits there. 2. Keep scoped package identities slash-delimited so Windows sandbox setup patches their peers. 3. Regenerate license attribution when any source input changes instead of only on lock updates.
…range `@aws-sdk/client-bedrock-runtime` forced `@smithy/node-http-handler` at `*`, so every bedrock sandbox received the newest published handler regardless of the `@smithy/*` generation it was built against. The 3.422.0 sandbox declares `^2.1.5`, got a v4 handler, and that handler resolved its own `@smithy/core` v3 independently of the rest of the sandbox, so loading it threw `MODULE_NOT_FOUND` on `@smithy/core/protocols`. Which majors collide depends on what the registry published inside the install's `minimumReleaseAge` window, so the sandbox broke on a day when nothing in the repository had changed. A forced transitive now takes an explicit `version` first, then the range the installed package declares for itself, and only then the latest-version pin. Entries genuinely absent from their parent's manifest (`moleculer` → `bluebird`, `@prisma/client` → `mongodb`) are unaffected. The sandbox assertion accepted any `semver.validRange`, which `*` satisfies, and now requires the injected range to be a subset of what the client itself declares.
Bun sets `npm_execpath` to its own executable rather than to a JavaScript entry point, so the bootstrap ran `node <60 MB binary> install` and Node exited on a bare `SyntaxError` while parsing it as CommonJS. The version check that reports `Could not install Bun` never ran, leaving a parser error as the only output. The branch is reached whenever the bootstrapped binary is missing, which is what `bun install --ignore-scripts` leaves behind, so any Bun-launched script in that state failed with an unrelated error.
Each vendored bundle contains code from several packages, but the plugin emitted `modules[0].licenseText` — whichever package the bundler happened to order first. That order follows the installer's `node_modules` layout, so replacing Yarn with Bun reshuffled it and `@apm-js-collab/code-transformer` shipped Mozilla's BSD-3-Clause text in place of its own Apache-2.0 license, while every other package in the bundle went unattributed. Each `LICENSE` now carries every bundled package's text, name-sorted so the result no longer depends on the installer. A package that publishes no license file falls back to a canonical template chosen by the SPDX id its manifest declares, which is how `crypto-randomuuid` resolves, and an id with no template fails the build instead of shipping a bundle with a missing license.
1. The lock walk followed only `dependencies` and `optionalDependencies`, so a production package's peers were treated as dev-only and went unattributed. `@datadog/openfeature-node-server` is an optional dependency whose peer `@openfeature/server-sdk` is satisfied by a range declared under `devDependencies`; `bun install --production` resolves it, so it and `@openfeature/core` ship inside the OCI package with no license row. A peer nobody installed has no lock entry and still drops out. 2. The generated CSV had moved to LF while `.github/vendored-dependencies.csv` stayed CRLF. The license workflow appends the second file to the first, so the result carried mixed endings, editors re-LF it on save, and the release tooling re-committed it on every run.
`bun audit --ignore <id>` accepts ids it never saw without complaining, so an inline ignore list keeps suppressing an advisory long after the dependency was patched and nothing reports that the entry is dead. Three of the five ignored advisories were in that state: `form-data`, `path-to-regexp`, and `undici` each had a patched release inside the range their parents already declare, so re-resolving those three lock entries clears them and the ignores go away. Accepted advisories now live in `.github/audit-allowlist.json`, one entry per directory with a written reason, and the wrapper fails both on an advisory that is not accepted and on an acceptance that no longer matches anything. The threshold is per directory because `vendor` is bundled and shipped, so a moderate advisory there reaches customers, while `--audit-level=high` had been filtering it out.
The cassette reference told readers to run `PLUGINS=<integration> yarn services` to install a missing version fixture. Yarn is no longer installed, so the command fails outright; the fixture stays missing and the spec keeps reporting a broken module. The guard test that keeps yarn out of the tree fails on the reference too.
…list The generator fed `.github/vendored-dependencies.csv` through the same map as the lockfile walk, so its rows were sorted in among the installed packages. Those two rows attribute code copied into this repo — a regex from `is-git-url`, handler-path logic from the AWS Lambda runtime interface client — not packages the artifact resolves. Alphabetical placement asserts we ship an `is-git-url` that no lockfile has ever contained, which is the opposite of what an attribution file is read for. They now trail the sorted dependencies, the layout `dd-license-attribution` produced by appending the file. A name appearing in both lists would emit two rows for one component, and `check_licenses.js` compares name sets, so nothing downstream would catch it. The generator now fails instead.
Severity is ranked by index into a fixed list, and an unrecognized string yields -1, which sorts below every configured threshold. A renamed or newly added bun severity therefore skips every advisory carrying it and the job reports a clean tree, which is the failure the wrapper exists to prevent rather than one it can absorb quietly.
The workflow only watched lockfiles, so a PR could add or edit an entry in `.github/audit-allowlist.json` — or change `scripts/audit.js` itself — and merge without ever running the wrapper that checks every exemption still carries a reason and is still reported. An exemption is only as good as the run that re-checks it, so the policy and the enforcing script now trigger the workflow alongside the lockfiles they gate. Refs: #8386
The setup action hard-coded `bun@1.3.14` while `package.json` holds the pin that `scripts/bun.js` enforces. Dependabot updates the devDependency without touching the action, and nothing then runs the wrong Bun — `getBunBinary()` bootstraps the pinned one locally — but every CI job pays for that bootstrap until someone notices the two numbers drifted. Read the pin instead of restating it. The spec assertion moves with it: it used to compare the two literals, and now requires the action to carry no literal at all. The playwright Dockerfile pin stays literal because its `FROM` line needs the image digest too. Refs: #8386
The langchain entry pinned `@langchain/openai@0.0.34`'s copy of `@langchain/core` through a Yarn-style selective key (`@langchain/openai@0.0.34/@langchain/core`). Bun only honours a bare package name: it copies the key into the manifest and never applies it. Verified against the pinned Bun — a selective key leaves `@langchain/core` at 0.2.36 while an equivalent top-level override moves it — so the line read as an enforced pin while resolution was untouched, and the old spec asserted the dead key was present rather than that it did anything. 1. Remove the override. 0.0.34's own declared range already lands inside `^0.2.0`, which is what the spec now asserts. A top-level override is not the fix: it is workspace-wide and would drag langgraph's `>=1.1.16` sandboxes down to the 0.2 line. 2. Reject any override key that is not a bare package name at generation time, so the next selective or nested override fails loudly instead of reading as enforced. Refs: #8386
Mergegate failed with "CODEOWNERS missing" for `.github/audit-allowlist.json`, the fourth such failure on this branch after `docs/bun.lock`, `versions/bunfig.toml` and `vendor/bun.lock`. The main lint does run a codeowners audit, but only over five globs — `**/*.spec.js`, `benchmark/sirun/**`, `.agents/**`, `.claude/**` and `integration-tests/**` — so no local gate covered the tree this branch keeps adding files to, and every gap had to be found by pushing. 1. Default the whole `/.github/` tree to the platform team instead of listing `/.github/actions/` and `/.github/all-green/` one directory at a time. GitHub applies the last matching rule, so the per-product entries further down keep the workflows and directories they already claim. This closes 16 pre-existing gaps, among them the issue templates, `dependabot.yml`, and the audit, flakiness, platform and release-proposal workflows. 2. Add `.github/**` and `scripts/**` to `lint:codeowners:ci`, so the next unowned file fails locally instead of in mergegate. Drive-by: move the `/docs/bun.lock` and `/vendor/bun.lock` rules out of the middle of the workflow block, where they had nothing to do with their neighbours, into the Language Platform section. Ownership is unchanged; no other rule matches either path. Refs: #8386
## Summary - keep sparse CI and OCI installs complete - restore fail-closed Bun lock traversal and exact attribution refresh - remove redundant fallback branches exposed by current lint ## Why Sparse jobs invoke the Node setup action, which reads package.json to select the pinned Bun version. The OCI build also needs trusted dependency lifecycle scripts while skipping only the package prepare hook. The extracted lock reader had lost the previous parser validation and ancestor/workspace resolution, allowing malformed or incomplete locks to silently shrink license attribution. Single-version rows also retained stale registry metadata. ## Test plan - npm run test:scripts - npm run lint - node scripts/generate-3rdparty-licenses.js --check - changed-line c8 coverage for the affected scripts
## Summary Refresh the root Bun lock after the eslint-plugin-unicorn bump. Classify Bun locks correctly in release changelogs. Narrow GitHub ownership and remove the obsolete consumer-install action. ## Why The rebased tree failed every frozen install because master advanced a dependency without the PR's new lockfile. Master's release tooling also reintroduced Yarn-only assumptions, while recursive GitHub ownership claimed workflows that already have product owners. ## Test plan Complete script suite, full repository lint, frozen Bun install, changed-line coverage, generated license check, and shell syntax checks.
## Summary Fresh license regeneration and dependency audits now pass against the Bun lockfiles. ## Why The license workflow invoked the Bun lock parser without installing jsonc-parser. New high-severity advisories also made the root and docs audits reject stale lock resolutions. ## Test plan - `bun install --frozen-lockfile --ignore-scripts` in root and docs - `node scripts/audit.js . docs` - `node scripts/generate-3rdparty-licenses.js` - `npm run test:scripts` - `npm run lint` PR-URL: #8386
## Summary Exact plugin pins and reviewed copyright attribution remain authoritative; audit and package-size jobs use current Bun behavior. ## Why Flexible latest ranges let plugin sandboxes drift above the tested pin, registry author fields overwrite reviewed copyright, and stale workarounds obscure Bun 1.3.14 behavior. Dependabot now supports bun.lock, so a scheduled wrapper duplicates upstream advisory monitoring. ## Test plan - npm run test:scripts - ./node_modules/.bin/mocha scripts/test/generate-3rdparty-licenses.spec.js packages/dd-trace/test/plugins/versions.spec.js - PLUGINS=pino npm run test:plugins - npm run lint - actionlint .github/workflows/audit.yml .github/workflows/instrumentation.yml .github/workflows/project.yml - coverage-diff.js HEAD
Dependabot supports Bun version updates but not security updates, so newly disclosed advisories do not change bun.lock or trigger a pull-request audit.
* test(plugins): remove obsolete Bun dependency workarounds Bun 1.3.14 and the current isolated installer produce valid dependency graphs for these packages without the added direct dependencies and overrides. * test(plugins): remove stale workaround assertions `test:scripts` still asserted Knex, Limitd, and AI dependency accommodations after those configurations were removed, so the release-scripts job failed.
A second Bun invocation is unsafe when the generated workspace state from the first failure remains in place.\n\nAllow one second workflow attempt for transient runner failures, and start both from an empty root node_modules tree.
Bun ignores advisory IDs without checking whether they still match a current finding. Parse the complete report so stale exceptions, package mismatches, and command failures cannot pass unnoticed.\n\nUse high as the default threshold and lower it only for the vendored dependency tree.
Overall package sizeSelf size: 7.81 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.3.3 | 125.43 kB | 441.68 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: a88fe65 | Docs | Datadog PR Page | Give us feedback! |
BenchmarksBenchmark execution time: 2026-08-11 15:40:18 Comparing candidate commit a88fe65 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 2321 metrics, 37 unstable metrics.
|
e109dd9 to
4f3d636
Compare
What does this PR do?
Audit every lockfile through one parser with a high-severity default. The vendored dependency tree explicitly lowers the threshold to moderate.
Motivation
Bun's native ignore option cannot detect stale exceptions or package mismatches. Command failures and incomplete reports must also remain failures.
Additional Notes
Stacked on #8386.